﻿using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
//using System.Services;
using System.Threading.Tasks;

namespace System.Events
{
    /// <summary>
    /// The default implementation of <see cref="IEventPublisher"/>.
    /// </summary>
    [Obsolete("Pleas try to implement smth more fleible: http://forum.codecall.net/news/19236-braindump-event-aggregator-pattern.html")]
    [Shared]
    [Export(typeof(IEventPublisher))]
    public class DefaultEventPublisherService : IEventPublisher
    {
        private IEnumerable<Lazy<IMessageRecipient, MessageRecipientExportAttribute>> recipients;

        //private ILoggerService Logger;

        readonly Dictionary<Type, object> subjects = new Dictionary<Type, object>();
        readonly object @lock = new object();

        [ImportingConstructor]
        public DefaultEventPublisherService(
            //[Import]
            //ILoggerService Logger,
            [ImportMany]
            IEnumerable<Lazy<IMessageRecipient, MessageRecipientExportAttribute>> lazyRecipients)
        {
            //this.Logger = Logger;
            this.recipients = lazyRecipients;

            SubscribeAutodiscoveredRecipients();
        }

        /// <summary>
        /// Gets the event for use by subscribers.
        /// </summary>
        /// <typeparam name="TEvent">The type of the event.</typeparam>
        /// <returns></returns>
        public IObservable<TEvent> GetEvent<TEvent>()
        {
            object subject;
            var eventType = typeof(TEvent);

            if (!subjects.TryGetValue(eventType, out subject))
            {
                lock (@lock)
                {
                    if (!subjects.TryGetValue(eventType, out subject))
                    {
                        subjects[eventType] = subject = new Subject<TEvent>();
                    }
                }
            }
            return ((Subject<TEvent>)subject).AsObservable();
        }

        /// <summary>
        /// Publishes the specified sample event.
        /// </summary>
        /// <typeparam name="TEvent">The type of the event.</typeparam>
        /// <param name="sampleEvent">The sample event.</param>
        public void Publish<TEvent>(TEvent sampleEvent)
        {
            var subject = TryGetSubject<TEvent>(sampleEvent);

            if (subject != null)
                subject.OnNext(sampleEvent);
        }

        private Subject<TEvent> TryGetSubject<TEvent>(TEvent @event)
        {
            object subject;

            return subjects.TryGetValue(typeof(TEvent), out subject) ? (Subject<TEvent>)subject : null;
        }


        public void Publish<TEvent>(TEvent @event, Action FinallyAction)
        {
            var subject = TryGetSubject<TEvent>(@event);

            if (subject == null)
                FinallyAction();
            else
            {
                subject.Finally(FinallyAction);
                subject.OnNext(@event);
                subject.OnCompleted();
            }
        }

        public Task PublishAsync<TEvent>(TEvent @event, Action FinallyAction)
        {
            var subject = TryGetSubject<TEvent>(@event);

            var task = Task.Run(() =>
                {

                    if (subject == null)
                        FinallyAction();
                    else
                    {
                        if(FinallyAction !=null)
                            subject.Synchronize().Finally(FinallyAction);
                        subject.OnNext(@event);
                    }
                });
            return task;
        }

        private void SubscribeAutodiscoveredRecipients()
        {
            if (recipients == null || recipients.Count() <= 0)
                return;

            var miGetEvent = this.GetType().GetMethod("GetEvent");
            var midefGetEvent = miGetEvent.GetGenericMethodDefinition();
            var oeType = typeof(ObservableExtensions);
            var oeSubscribeMethod = oeType.GetMethods("Subscribe").FirstOrDefault(mi => mi.GetParameters().Count() == 2);// ("Subscribe");
            var oeSubscribeMethodDefinition = oeSubscribeMethod.GetGenericMethodDefinition();

            foreach (var lazy in recipients)
            {
                //Logger.LogFormat(LogSeverity.Information, this.GetType().ToString(), "Auto Subscribing: {0} for event: {1}", lazy.Metadata.Name, lazy.Metadata.MessageType);
                Type recipientMessageType = lazy.Metadata.MessageType;

                //create and call this.GetEvent< *recipientMessageType* >()
                var genericGetEvent = midefGetEvent.MakeGenericMethod(recipientMessageType);
                var getEventResult = genericGetEvent.Invoke(this, new object[] { }); //IObservable< *recipientMessageType* >

                Action<object> genericAxtion = (o) =>
                    {
                        //Logger.LogFormat(LogSeverity.Verbose, lazy.Metadata.Name ?? lazy.Metadata.ContractName, "{0}Processing Autodiscovered Message: [{2}] [{1}]", lazy.Metadata.Force ? "Force " : string.Empty, o.GetType(), lazy.IsValueCreated?"Subscriber Already Created" : "Subscriber Was Not Created");
                        if (lazy.IsValueCreated || lazy.Metadata.Force)
                        lazy.Value.ProcessMessage(o);
                    };

                //get and invoke Subscribe <>( Action<> )
                var genericSubscribeEvent = oeSubscribeMethodDefinition.MakeGenericMethod(recipientMessageType);
                genericSubscribeEvent.Invoke(null, new object[] { getEventResult, genericAxtion });
            }
        }

        //public void StartService()
        //{
        //}

        //public void StopService()
        //{
        //}

        //public void Dispose()
        //{
        //}
    }

}
